package com.src.java.classloader;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

/**
 * Dynamically load class using specified jar - This class will try the child
 * class loader first and then the parent. *
 * 
 * @author GPS
 * 
 */
public class CustomClassLoader extends ClassLoader {
	private ChildClassLoader childClassLoader;

	public CustomClassLoader(List<URL> classpath) {
		// Returns the context ClassLoader for this Thread - the default is the
		// ClassLoader context of the parent Thread
		super(Thread.currentThread().getContextClassLoader());
		URL[] urls = classpath.toArray(new URL[classpath.size()]);
		childClassLoader = new ChildClassLoader(urls, new DetectClass(this.getParent()));
	}

	@Override
	protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
		System.out.println("In CustomClassLoader - loadClass - " + name);
		try {
			// first we try to find a class inside the child class loader
			return childClassLoader.findClass(name);
		} catch (ClassNotFoundException e) {
			// didn't find it, try the parent
			return super.loadClass(name, resolve);
		}
	}

	/**
	 * This class delegates (child then parent) for the findClass method for a
	 * URLClassLoader. We need this because findClass is protected in
	 * URLClassLoader
	 */
	private static class ChildClassLoader extends URLClassLoader {
		private DetectClass realParent;

		public ChildClassLoader(URL[] urls, DetectClass realParent) {
			super(urls, null);
			this.realParent = realParent;
		}

		@Override
		public Class<?> findClass(String name) throws ClassNotFoundException {

			System.out.println("In ChildClassLoader - findClass - " + name);
			Class<?> loaded = super.findLoadedClass(name);
			if (loaded != null) {
				return loaded;
			}
			try {
				// first try to use the URLClassLoader findClass
				return super.findClass(name);
			} catch (NoClassDefFoundError e) {
				// if that fails, we ask our real parent class loader to load
				// the
				// class (we give up)
				System.out.println("In ChildClassLoader - calling realParent.loadClass " + e.getMessage());
				return realParent.loadClass(name);
			} catch (ClassNotFoundException e) {
				// if that fails, we ask our real parent class loader to load
				// the
				// class (we give up)
				System.out.println("In ChildClassLoader - calling realParent.loadClass " + e.getMessage());
				return realParent.loadClass(name);
			}
		}

	}

	/**
	 * The Detect class loader subclass must define the methods findClass and
	 * loadClassData to load a class from the network. Once it has download the
	 * bytes that make up the class, it should use the method defineClass to
	 * create a class instance.
	 * 
	 * 
	 */
	private static class DetectClass extends ClassLoader {
		public DetectClass(ClassLoader parent) {
			super(parent);
		}

		@Override
		public Class<?> findClass(String name) throws ClassNotFoundException {
			return super.findClass(name);
		}
	}
}